﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Collections;
using ProjTemp.Common.Logging;
using System.ComponentModel.Composition;

namespace ProjTemp.Desktop.Common.ResourceManagement
{
    [Export(typeof(IResourceManage))]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public class ResourceManager : IResourceManage
    {
        #region MPC

        private ILogger _logger;
        private object _syncObject;
        private ResourceDictionary _globalResourceDictionary;
        private Dictionary<string, ResourceDictionary> _subjectResourceDictionary;
        private Dictionary<string, Dictionary<string, ResourceDictionary>> _aspectResourceDictionary;
        private Dictionary<string, string> _defaultSubjectAspect;

        [ImportingConstructor]
        public ResourceManager(ILogger logger)
        {
            if (logger == null)
                throw new ArgumentNullException("logger");

            this._logger = logger;
            this._logger.LogType = typeof(ResourceManager);
            this._syncObject = new object();
            this._globalResourceDictionary = new ResourceDictionary();
            this._subjectResourceDictionary = new Dictionary<string, ResourceDictionary>();
            this._aspectResourceDictionary = new Dictionary<string, Dictionary<string, ResourceDictionary>>();
            this._defaultSubjectAspect = new Dictionary<string, string>();
        }

        #endregion

        #region IResourceManage

        public void AddResourceDictionary(System.Windows.ResourceDictionary resourceDictionary, string subject = null, string aspect = null)
        {
            _logger.InfoFormat("Add resource dictionary, subject:{0}, aspect:{1}.", subject ?? string.Empty, aspect ?? string.Empty);

            try
            {
                if (resourceDictionary == null)
                    throw new ArgumentNullException("resourceDictionary");

                subject = this.Normalize(subject);
                aspect = this.Normalize(aspect);
                lock (this._syncObject)
                {
                    //check the subject and aspect are valid or not
                    CheckValid(subject, aspect);
                    //resolve the corresponded resource dictionary.
                    var correspondedResourceDictionary = this.ResolveCorrespondedResourceDictionary(subject, aspect);
                    //apply source dictionary
                    this.ApplyResourceDictionary(resourceDictionary, correspondedResourceDictionary);
                }
            }
            catch (Exception ex)
            {
                _logger.Error("AddResourceDictionary", ex);
            }
        }

        public void AddResourceEntry(object key, object value, string subject = null, string aspect = null)
        {
            _logger.InfoFormat("Add resource entry, key:{0}, value:{1}, subject:{2}, aspect:{3}.", key == null ? string.Empty : key.ToString(), value == null ? string.Empty : value.ToString(), subject ?? string.Empty, aspect ?? string.Empty);

            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                if (value == null)
                    throw new ArgumentNullException("value");

                subject = this.Normalize(subject);
                aspect = this.Normalize(aspect);
                lock (this._syncObject)
                {
                    //check the subject and aspect are valid or not
                    CheckValid(subject, aspect);
                    //resolve the corresponded resource dictionary.
                    var correspondedResourceDictionary = this.ResolveCorrespondedResourceDictionary(subject, aspect);
                    //apply source entry
                    this.ApplyResourceEntry(key, value, correspondedResourceDictionary);
                }
            }
            catch (Exception ex)
            {
                _logger.Error("AddResourceEntry", ex);
            }
        }

        public object GetResourceEntryValue(object key, string subject = null, string aspect = null)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");

                subject = this.Normalize(subject);
                aspect = this.Normalize(aspect);
                lock (this._syncObject)
                {
                    //check the subject and aspect are valid or not
                    CheckValid(subject, aspect);
                    //resolve the corresponded resource dictionary.
                    var correspondedResourceDictionary = this.ResolveCorrespondedResourceDictionary(subject, aspect, false);
                    //apply source entry
                    return correspondedResourceDictionary.Contains(key) ? correspondedResourceDictionary[key] : null;
                }
            }
            catch (Exception ex)
            {                
                _logger.Error("GetResourceEntryValue", ex);
                return null;
            }
        }

        public void SetDefaultAspect(string subject, string aspect)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(subject))
                    throw new ArgumentNullException("subject");
                if (string.IsNullOrWhiteSpace(aspect))
                    throw new ArgumentNullException("aspect");

                subject = this.Normalize(subject);
                aspect = this.Normalize(aspect);
                lock (this._syncObject)
                {
                    //check the subject and aspect are valid or not
                    CheckValid(subject, aspect);
                    if (!this._aspectResourceDictionary.ContainsKey(subject))
                        throw new InvalidOperationException(string.Format("Subject not existed:{0}", subject));
                    var subjectResourceDictionary = this._aspectResourceDictionary[subject];
                    if(!subjectResourceDictionary.ContainsKey(aspect))
                        throw new InvalidOperationException(string.Format("Aspect not existed:{0}", aspect));

                    _logger.InfoFormat("Set {0} to the default aspect of subject {1}", subject, aspect);
                    if (this._defaultSubjectAspect.ContainsKey(subject))
                        this._defaultSubjectAspect[subject] = aspect;
                    else
                        this._defaultSubjectAspect.Add(subject, aspect);
                }
            }
            catch (Exception ex)
            {
                _logger.Error("SetDefaultAspect", ex);
            }
        }

        public string GetDefaultAspect(string subject)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(subject))
                    throw new ArgumentNullException("subject");

                subject = this.Normalize(subject);
                lock (this._syncObject)
                {
                    return this._defaultSubjectAspect.ContainsKey(subject) ? this._defaultSubjectAspect[subject] : null;
                }
            }
            catch (Exception ex)
            {
                _logger.Error("SetDefaultAspect", ex);
                return null;
            }
        }

        public void ApplyToResourceDictionary(System.Windows.ResourceDictionary resourceDictionary, string subject = null, string aspect = null)
        {
            _logger.InfoFormat("Apply to resource dictionary, subject:{0}, aspect:{1}.", subject ?? string.Empty, aspect ?? string.Empty);

            try
            {
                if (resourceDictionary == null)
                    throw new ArgumentNullException("resourceDictionary");

                subject = this.Normalize(subject);
                aspect = this.Normalize(aspect);
                lock (this._syncObject)
                {
                    //check the subject and aspect are valid or not
                    CheckValid(subject, aspect);
                    if (string.IsNullOrWhiteSpace(subject))
                    {
                        //apply the global and all the non-aspect resource dictionary
                        this.ApplyResourceDictionary(this._globalResourceDictionary, resourceDictionary);
                        foreach (var key in this._subjectResourceDictionary.Keys)
                        {
                            var subjectResourceDictionary = this._subjectResourceDictionary[key];
                            this.ApplyResourceDictionary(subjectResourceDictionary, resourceDictionary);
                        }
                        //apply the default aspect resource dictionary
                        foreach (var key in this._defaultSubjectAspect.Keys)
                        {
                            var value = this._defaultSubjectAspect[key];
                            //resolve the corresponded resource dictionary.
                            var correspondedResourceDictionary = this.ResolveCorrespondedResourceDictionary(key, value, false);
                            //apply source dictionary
                            this.ApplyResourceDictionary(correspondedResourceDictionary, resourceDictionary);
                        }
                    }
                    else
                    {
                        //resolve the corresponded resource dictionary.
                        var correspondedResourceDictionary = this.ResolveCorrespondedResourceDictionary(subject, aspect, false);
                        //apply source dictionary
                        this.ApplyResourceDictionary(correspondedResourceDictionary, resourceDictionary);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error("ApplyToResourceDictionary", ex);
            }
        }

        #endregion

        #region Private methods

        private string Normalize(string str)
        {
            return str == null ? null : str.Trim().ToLower();
        }

        private void CheckValid(string subject, string aspect)
        {
            if (string.IsNullOrWhiteSpace(subject))
                return;
            if (!this._subjectResourceDictionary.ContainsKey(subject) && !this._aspectResourceDictionary.ContainsKey(subject))
                return;

            if (this._subjectResourceDictionary.ContainsKey(subject) && !string.IsNullOrWhiteSpace(aspect))
                throw new InvalidOperationException(string.Format("Subject {0} has no aspects.", subject));

            if (this._aspectResourceDictionary.ContainsKey(subject) && string.IsNullOrWhiteSpace(aspect))
                throw new InvalidOperationException(string.Format("Subject {0} has aspects.", subject));
        }

        private ResourceDictionary ResolveCorrespondedResourceDictionary(string subject, string aspect, bool createNew = true)
        {
            if(string.IsNullOrWhiteSpace(subject) && !string.IsNullOrWhiteSpace(aspect))
                throw new ArgumentException(string.Format("subject {0} should not be invalid while aspect {1} is valid.", subject, aspect));

            var correspondedResourceDictionary = this._globalResourceDictionary;
            if (!string.IsNullOrWhiteSpace(subject))
            {
                if (string.IsNullOrWhiteSpace(aspect))
                {
                    if (!this._subjectResourceDictionary.ContainsKey(subject))
                    {
                        if (!createNew)
                        {
                            _logger.WarnFormat("Resource dictionary for subject: {0} is not added.", subject);
                            return new ResourceDictionary();
                        }

                        _logger.InfoFormat("Create resource dictionary for subject: {0}", subject);
                        this._subjectResourceDictionary.Add(subject, new ResourceDictionary());
                    }

                    correspondedResourceDictionary = this._subjectResourceDictionary[subject];
                }
                else
                {
                    if (!this._aspectResourceDictionary.ContainsKey(subject))
                    {
                        if (!createNew)
                        {
                            _logger.WarnFormat("Resource dictionary for subject: {0}, aspect:{1} is not added.", subject, aspect);
                            return new ResourceDictionary();
                        }

                        this._aspectResourceDictionary.Add(subject, new Dictionary<string, ResourceDictionary>());
                    }

                    var subjectResourceDictionary = this._aspectResourceDictionary[subject];
                    if (!subjectResourceDictionary.ContainsKey(aspect))
                    {
                        if (!createNew)
                        {
                            _logger.WarnFormat("Resource dictionary for subject: {0}, aspect:{1} is not added.", subject, aspect);
                            return new ResourceDictionary();
                        }

                        _logger.InfoFormat("Create resource dictionary for subject: {0}, aspect:{1}", subject, aspect);
                        if (subjectResourceDictionary.Keys.Count == 0)
                        {
                            _logger.InfoFormat("Set {0} to the default aspect of subject {1}", subject, aspect);
                            this._defaultSubjectAspect.Add(subject, aspect);
                        }
                        subjectResourceDictionary.Add(aspect, new ResourceDictionary());
                    }

                    correspondedResourceDictionary = subjectResourceDictionary[aspect];
                }
            }

            return correspondedResourceDictionary;
        }

        private void ApplyResourceDictionary(ResourceDictionary newRd, ResourceDictionary oldRd)
        {
            foreach (DictionaryEntry re in newRd)
                ApplyResourceEntry(re.Key, re.Value, oldRd);
        }

        private void ApplyResourceEntry(object key, object value, ResourceDictionary oldRd)
        {
            if (oldRd.Contains(key))
            {
                _logger.WarnFormat("resource is overrided, key:{0}, oldValue:{1}, newValue:{2}.",
                    key == null ? "(null)" : key.ToString(),
                    oldRd[key] == null ? "(null)" : oldRd[key].ToString(),
                    value == null ? "(null)" : value.ToString());
                oldRd.Remove(key);
            }

            oldRd.Add(key, value);
        }
        #endregion
    }
}
